import axios from "axios"
// 导入axios内置定义好的类型
import type { AxiosInstance } from "axios"
// 把接口抽离出去，然后再进行导入
import type { SYRequestConfig } from "./type"

// 对依赖的第三方库都会对其进行二次封装 -->
// 避免某一天第三方库不在维护，换库或者换框架的时候只需修改封装好的代码就行

/**
 * 拦截器: 蒙版Loading/token/修改配置
 *
 * 两个难点:
 *  1.拦截器进行精细控制
 *    > 全局拦截器
 *    > 实例拦截器
 *    > 单次请求拦截器
 *
 *  2.响应结果的类型处理(泛型)
 */

class SYRequest {
  instance: AxiosInstance

  // request实例 => axios的实例
  constructor(config: SYRequestConfig) {
    this.instance = axios.create(config)

    // 【全局拦截器】
    // 每一个instance实例都添加拦截器
    // new 创建出来的请求实例默认都会进行拦截
    this.instance.interceptors.request.use(
      (config) => {
        // console.log("全局请求成功的拦截")
        return config
      },
      (err) => {
        // console.log("全局请求失败的拦截")
        return err
      }
    )
    this.instance.interceptors.response.use(
      (res) => {
        // console.log("全局响应成功的拦截")
        return res
        // return res.data
      },
      (err) => {
        // console.log("全局响应失败的拦截")
        return err
      }
    )

    // 【实例拦截器】
    // 针对特定的syRequest2请求实例添加拦截器;
    // 拦截器可以设多个，不互相干扰，并且是同时执行的
    // > 先判断有没有传入配置
    // 法一：
    // if (config.interceptors) { // 类型缩小
    //   this.instance.interceptors.request.use(
    //     config.interceptors.requestSuccessFn,
    //     config.interceptors.requestFailureFn
    //   );
    //   this.instance.interceptors.response.use(
    //     config.interceptors.responseSuccessFn,
    //     config.interceptors.responseFailureFn
    //   );
    // }
    // 法二：
    // 因为use里面本来传入的函数就是可选的，可以
    // 所以这里可以用可选链，要么是函数，要么是undefined
    this.instance.interceptors.request.use(
      (config as any).interceptors?.requestSuccessFn,
      config.interceptors?.requestFailureFn
    )
    this.instance.interceptors.response.use(
      config.interceptors?.responseSuccessFn,
      config.interceptors?.responseFailureFn
    )
  }

  // 【单次请求拦截器】
  // 封装网络请求地方方法
  // request(config: AxiosRequestConfig) {
  //   return this.instance.request(config);
  // }
  // request也想有一定的拦截器
  request<T = any>(config: SYRequestConfig<T>) {
    /* 
       不可以将拦截器添加到这个里面去
       this.instance.interceptors.request.use();
       > 添加相当于把拦截器添加到这个实例里面this.instance
         因为每一个请求是共享一个实例的，上一个加的拦截器会一直生效
         就算下一个请求没有添加到拦截器里面，但也会在这里一直生效
       > 拦截器的本质就是钩子函数，在一定时期进行回调
    */
    //【单次请求的成功拦截处理】
    if (config.interceptors?.requestSuccessFn) {
      // 判断有没有请求成功的拦截,有就拿到请求成功的函数，对其进行回调
      // 回调返回新的config，接收原来改变之后的config
      config = config.interceptors.requestSuccessFn(config)
    }
    /* 
      return this.instance.request(config);
      这样写直接返回request，并不知道request什么时候有结果;
      得在响应结果来的时候，拿到响应成功的函数，手动对其进行调用
      > 用promise进行包裹,在resolve和reject之前对响应进行拦截处理

      但new Promise得给定一个类型AxiosResponse，不然结果是unknown
      如果想返回的是res中的data，可以在全局响应拦截器中直接return res.data;
      但在这拿到就是具体的数据了,并不是类型AxiosResponse
      > 用泛型参数，将所需要返回的数据类型传递进去的
    */
    return new Promise<T>((resolve, reject) => {
      this.instance
        .request<any, T>(config)
        .then((res) => {
          //【单次响应的成功拦截处理】
          if (config.interceptors?.responseSuccessFn) {
            // 如果做出改变，就拿到新的res
            // 需要注意要将去type.ts中把SYRequestConfig类型改为泛型
            // 不然这里响应成功回调默认返回的是AxiosResponse赋值不了res;
            // 使响应成功返回的值类型是传入的值T
            res = config.interceptors.responseSuccessFn(res)
          }
          resolve(res)
          /* 
            resolve(res);
            this.instance.request(config)的res返回的类型是 AxiosResponse
            这次网络请求的类型是要求是new Promise传进来的T类型;
            > 得想办法让res的类型不再是 AxiosResponse类型，而是类型T
            > 法一：resolve(res as any as T)
            > 法二：直接改变request的类型，源码就写着可改变的
                    this.instance.request<any, T>(config)
          */
        })
        .catch((err) => {
          //【单次响应的失败拦截处理】
          if (config.interceptors?.responseFailureFn) {
            // 如果做出改变，就拿到新的res
            err = config.interceptors.responseFailureFn(err)
          }
          reject(err)
        })
    })
  }

  get<T = any>(config: SYRequestConfig<T>) {
    return this.request({ ...config, method: "GET" })
  }
  post<T = any>(config: SYRequestConfig<T>) {
    return this.request({ ...config, method: "POST" })
  }
  delete<T = any>(config: SYRequestConfig<T>) {
    return this.request({ ...config, method: "DELETE" })
  }
  patch<T = any>(config: SYRequestConfig<T>) {
    return this.request({ ...config, method: "PATCH" })
  }
}

export default SYRequest
